package com.example.airhockey3d

import android.R.attr
import android.content.Context
import android.opengl.GLES30.*
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10


class AirHockey3DView(context : Context) : GLSurfaceView(context) {

    init {
        setEGLContextClientVersion(3)
        setRenderer(AirHockeyRenderer())
    }

    private inner class AirHockeyRenderer : Renderer {
        private var program = 0;
        private var uMatrixLocation = 0;

        private val projectionMatrix = FloatArray(16)

        private val modelMatrix = FloatArray(16)

        private lateinit var vertexData: FloatBuffer;

        private var tableVerticesWithTriangles: FloatArray =
            floatArrayOf( // Order of coordinates: X, Y, R, G, B
                // Triangle Fan
                0f, 0f, 1f, 1f, 1f,
                -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,
                0.5f, -0.8f, 0.7f, 0.7f, 0.7f,
                0.5f, 0.8f, 0.7f, 0.7f, 0.7f,
                -0.5f, 0.8f, 0.7f, 0.7f, 0.7f,
                -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,

                // Line 1
                -0.5f, 0f, 1f, 0f, 0f,
                0.5f, 0f, 1f, 0f, 0f,

                // Mallets
                0f, -0.4f, 0f, 0f, 1f,
                0f, 0.4f, 1f, 0f, 0f
            )

        override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f)

            program = createProgram(loadShader(GL_VERTEX_SHADER, "simple_vertex_shader.glsl"),
                loadShader(GL_FRAGMENT_SHADER, "simple_fragment_shader.glsl"))

            // Initialize the vertex buffer
            vertexData = ByteBuffer.allocateDirect(tableVerticesWithTriangles.size * Float.SIZE_BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
            vertexData.put(tableVerticesWithTriangles)

            glUseProgram(program);

            uMatrixLocation = glGetUniformLocation(program, "u_Matrix");

            vertexData.position(0);
            glVertexAttribPointer(0,
                2, GL_FLOAT, false, 5 * Float.SIZE_BYTES, vertexData);

            glEnableVertexAttribArray(0);

            // Bind our data, specified by the variable vertexData, to the vertex
            // attribute at location A_COLOR_LOCATION.
            vertexData.position(2);
            glVertexAttribPointer(1,
                3, GL_FLOAT, false, 5 * Float.SIZE_BYTES, vertexData);

            glEnableVertexAttribArray(1);

        }

        override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
            glViewport(0, 0, width, height)


            Matrix.perspectiveM(
                projectionMatrix, 0, 45f,
                attr.width.toFloat() / attr.height.toFloat(), 1f, 10f
            )

            Matrix.setIdentityM(modelMatrix, 0)

            Matrix.translateM(modelMatrix, 0, 0f, 0f, -2.5f)
            Matrix.rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f)

            val temp = FloatArray(16)
            Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0)
            System.arraycopy(temp, 0, projectionMatrix, 0, temp.size)
        }

        override fun onDrawFrame(gl: GL10?) {
            // Clear the rendering surface.
            glClear(GL_COLOR_BUFFER_BIT);

            // Assign the matrix
            glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);

            // Draw the table.
            glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

            // Draw the center dividing line.
            glDrawArrays(GL_LINES, 6, 2);

            // Draw the first mallet.
            glDrawArrays(GL_POINTS, 8, 1);

            // Draw the second mallet.
            glDrawArrays(GL_POINTS, 9, 1);
        }

        private fun loadShader(type: Int, filename: String): Int {
            val shaderSource = readShaderFileFromAssets(context, filename)
            val shader = glCreateShader(type)
            glShaderSource(shader, shaderSource)
            glCompileShader(shader)
            val compiled = IntArray(1)
            glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0)
            if (compiled[0] == 0) {
                glDeleteShader(shader)
                throw RuntimeException("Could not compile shader: $filename")
            }
            return shader
        }

        private fun createProgram(vertexShader: Int, fragmentShader: Int): Int {
            val program = glCreateProgram()
            glAttachShader(program, vertexShader)
            glAttachShader(program, fragmentShader)
            glLinkProgram(program)
            val linkStatus = IntArray(1)
            glGetProgramiv(program, GL_LINK_STATUS, linkStatus, 0)
            if (linkStatus[0] == 0) {
                glDeleteProgram(program)
                throw RuntimeException("Could not link program")
            }
            return program
        }

        private fun readShaderFileFromAssets(context: Context, filename: String): String {
            val reader = BufferedReader(InputStreamReader(context.assets.open(filename)))
            val builder = StringBuilder()
            var line: String?
            while (reader.readLine().also { line = it } != null) {
                builder.append(line).append("\n")
            }
            reader.close()
            return builder.toString()
        }

    }
}